﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;

namespace FEClone
{

    /// <summary>
    /// Represents a 2-Dimensional tile-grid-based map and completes map-based operations
    /// </summary>
    [Serializable]
    public class Map
    {
        public List<List<MapCell>> TileMap = new List<List<MapCell>>();
        public List<Unit> UnitList = new List<Unit>();
        public Unit selectedUnit;
        public MapCell selectedMapCell;

        public int MapWidth;
        public int MapHeight;
        public int tileSize;
        [NonSerializedAttribute, System.Xml.Serialization.XmlIgnoreAttribute]
        public Texture2D tileSetTexture;
        [NonSerializedAttribute, System.Xml.Serialization.XmlIgnoreAttribute]
        private Texture2D moveRangeSquare;
        [NonSerializedAttribute, System.Xml.Serialization.XmlIgnoreAttribute]
        private Texture2D gridTexture;
        private TileEngine tileEngine;

        [NonSerializedAttribute, System.Xml.Serialization.XmlIgnoreAttribute]
        private SpriteBatch spriteBatch;

        public static Point left = new Point(-1, 0);
        public static Point up = new Point(0, -1);
        public static Point right = new Point(1, 0);
        public static Point down = new Point(0, 1);
        public static Point[] directions = { left, up, right, down };
        private Boolean moveComplete = true;
        private Boolean drawMovementRange = true;

        private ButtonState lastCursorState = ButtonState.Released;

        public Map(Texture2D myTexture, SpriteBatch myspritebatch, int myTileSize, int myMapWidth, int myMapHeight, Texture2D myMoveRangeSquare, Texture2D myGridTexture)
        {
            spriteBatch = myspritebatch;
            tileSetTexture = myTexture;
            tileSize = myTileSize;
            MapWidth = myMapWidth;
            MapHeight = myMapHeight;
            moveRangeSquare = myMoveRangeSquare;
            gridTexture = myGridTexture;

            tileEngine = new TileEngine(tileSetTexture, tileSize, MapWidth);

            //Make new blank map of the specified height and width
            for (int i = 0; i < MapHeight; i++)
            {
                TileMap.Add(new List<MapCell>());
                for (int j = 0; j < MapWidth; j++)
                {

                    TileMap[i].Add(new MapCell(0, tileSize));

                }
            }

            TileMap[0][1].TileID = 3;
            TileMap[0][1].movementCost = 2;

        }

        public void Draw(float scaleFactor)
        {

            DrawMapCells(scaleFactor);

            if (drawMovementRange == true && selectedUnit != null && selectedUnit.movementRangeGrid != null)
            {
                DrawMovementRange(scaleFactor, selectedUnit.movementRangeGrid);
            }

            DrawUnits(scaleFactor);


        }

        public void Update(Vector2 cursorPosition, ButtonState cursorState, float scaleFactor)
        {

            MapCell latestClickedMapCell = null;

            Unit latestClickedUnit = checkIfAnyUnitHasBeenClicked(cursorPosition, cursorState, scaleFactor);

            if (latestClickedUnit != null)
            {
                // Unit has been clicked.
                selectedUnit = latestClickedUnit;

                // We've selected a unit so clear the selected map cell ready for the user to click where they'd like
                // to move to and set moveComplete to false to indicate that a move is underway.
                selectedMapCell = null;
                moveComplete = false; // %%%PK Added
            }
            else
            {
                // Unit has not been clicked so check if they've clicked a map cell.
                latestClickedMapCell = checkIfAnyMapCellHasBeenClicked(cursorPosition, cursorState, scaleFactor);

                if (latestClickedMapCell != null)
                {
                    selectedMapCell = latestClickedMapCell;
                    if (selectedUnit != null)
                    {
                        // divide the x and y position in pixels by the tile size and scale factor to get the unitPosition on the grid
                        Point unitPosition = new Point((int)(selectedUnit.Position.X / (tileSize * scaleFactor)), (int)(selectedUnit.Position.Y / (tileSize * scaleFactor)));
                        selectedUnit.movementRangeGrid = calculateMovementRange(unitPosition, null, 4);

                        // Since a Unit is selected and they've just clicked a destintation cell, flag that move is now underway.
                        moveComplete = false;

                    }
                }

            }

            if (moveComplete == false && selectedMapCell != null && selectedUnit != null)
            {
                //check if valid move (mapcell unit is moving to is within unit movementrange)
                if (IsMapCellInUnitMovementRange(selectedUnit.movementRangeGrid, selectedMapCell) == true)
                {
                    drawMovementRange = false;
                    moveComplete = selectedUnit.Move(scaleFactor, selectedMapCell.Position, 3);
                }
            }
            else
            {
                drawMovementRange = true;
            }

            lastCursorState = cursorState;

        }

        public MapCell findMapCellAtPosition(Vector2 Position)
        {
            foreach (List<MapCell> row in TileMap)
            {
                foreach (MapCell cell in row)
                {
                    if (cell.Position == Position) return cell;

                }

            }

            return null;
        }

        public Point findMapCellInMap(MapCell mapcell)
        {
            foreach (List<MapCell> row in TileMap)
            {
                foreach (MapCell cell in row)
                {
                    if (mapcell == cell)
                    {
                        int i = row.IndexOf(cell);
                        int j = TileMap.IndexOf(row);

                        Point result = new Point(i, j);

                        return result;
                    }

                }


            }
            Point failure = new Point(-1, -1);
            return failure;
        }

        public Boolean IsMapCellInUnitMovementRange(Boolean[,] movementRangeGrid, MapCell cell)
        {
            Point cellCoords = findMapCellInMap(cell);

            if (movementRangeGrid[cellCoords.X, cellCoords.Y] == true)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public Unit checkIfAnyUnitHasBeenClicked(Vector2 cursorPosition, ButtonState cursorState, float scaleFactor)
        {
            foreach (Unit unit in UnitList)
            {
                if (unit.MapObjectHasBeenClicked(cursorState, lastCursorState, cursorPosition, scaleFactor))
                {
                    return unit;
                }
            }

            //No unit has been clicked, so return null
            return null;
        }

        public MapCell checkIfAnyMapCellHasBeenClicked(Vector2 cursorPosition, ButtonState cursorState, float scaleFactor)
        {
            foreach (List<MapCell> row in TileMap)
            {
                foreach (MapCell cell in row)
                {
                    if (cell.MapObjectHasBeenClicked(cursorState, lastCursorState, cursorPosition, scaleFactor) == true)
                    {
                        return cell;
                    }
                }
            }

            return null;
        }
        public void DrawUnits(float scaleFactor)
        {
            foreach (Unit u in UnitList)
            {
                u.Draw(scaleFactor);
            }

        }

        public void DrawMovementRange(float scalefactor, Boolean[,] movementRangeGrid)
        {
            Rectangle recto = new Rectangle(0, 0, tileSize, tileSize);

            for (int row = 0; row < MapHeight; row++)
            {
                for (int col = 0; col < MapWidth; col++)
                {
                    if (movementRangeGrid[col, row] == true)
                    {
                        spriteBatch.Draw(moveRangeSquare, TileMap[row][col].Position, recto, Color.White, 0f, Vector2.Zero, scalefactor, SpriteEffects.None, 0f);
                    }

                }

            }

        }

        public void WriteMovementGridToConsole(Boolean[,] movementRangeGrid)
        {
            for (int row = 0; row < MapHeight; row++)
            {
                for (int col = 0; col < MapWidth; col++)
                {
                    Console.Write(movementRangeGrid[col, row] ? "*" : ".");

                }

                Console.WriteLine("");
            }

        }

        public Boolean[,] calculateMovementRange(Point thisCharPosition, Character thisChar, int maxMovementValue)
        {
            Point directionsMovedSoFar = Point.Zero;

            // Create a Boolean map to show valid movement range.
            Boolean[,] movementRangeGrid = new Boolean[MapWidth, MapHeight];

            traverseAndUpdateMovementGridForCell(movementRangeGrid, thisCharPosition, thisChar, maxMovementValue, directionsMovedSoFar);

            return movementRangeGrid;
        }

        /// <summary>
        /// Recursively check each of the 4 possible movement directions surrounding this cell, if valid move then check whether there is sufficient remainingMovementValue for the character to cross it
        ///  and update the movementRangeGrid accordingly (true = char can move there; false = char can't move there)
        /// </summary>
        /// <param name="movementRangeGrid"> Boolean [,] of values indicating whether Character can move to the cell at this Map coordinate</param>
        /// <param name="cellPosition"> The Map cell to check the surrounding cells of.</param>
        /// <param name="thisChar"> The Character for which we are checking the movement range (note that this is passed in in case there are any attributes of the Character that might affect movmement ability</param>
        /// <param name="remainingMovementValue"> </param>
        /// <param name="directionsMovedSoFar"> A Point unit value indicating which directions have we have already moved on the current path - e.g. a value of (0,1) means we've moved down; (-1,-1) means we've moved left and moved up).  
        /// This value is used to make sure that if we've already moved in a particular direction (left/up/right/down), we prohibit moves in the opposite direction to avoid backtracking.
        /// </param>
        private void traverseAndUpdateMovementGridForCell(Boolean[,] movementRangeGrid, Point cellPosition, Character thisChar, int remainingMovementValue, Point directionsMovedSoFar)
        {
            Point newPosition;
            int movementCost;

            // Make sure there is any remaining range the character can move and if not, exit.
            if (remainingMovementValue <= 0)
                return;

            // Check all four directions (left/up/right/down) surrounding the cell and if valid move and sufficient remainingMovementValue for the character to cross it.
            foreach (Point direction in directions)
            {
                if (directionsMovedSoFar == Point.Zero ||
                    (directionsMovedSoFar.X != 0 && (direction.X + directionsMovedSoFar.X) != 0) ||
                    (directionsMovedSoFar.Y != 0 && (direction.Y + directionsMovedSoFar.Y) != 0))
                {
                    // We're moving in a direction that is consistent with any direction we've moved before.  So far so good.  Check if still on the map.
                    newPosition.X = cellPosition.X + direction.X;
                    newPosition.Y = cellPosition.Y + direction.Y;
                    if (newPosition.X < MapWidth && newPosition.Y < MapHeight && newPosition.X >= 0 && newPosition.Y >= 0)
                    {
                        // This is a valid cell in the map.  So far so good.  Check if sufficient movement range left for us to cross this map cell.
                        movementCost = TileMap[newPosition.Y][newPosition.X].movementCost;
                        if (movementCost != 0 && remainingMovementValue >= movementCost)
                        {
                            // Cell is not blocked (movementCost == 0 means cell is blocked) and sufficient movement range available so flag this cell as valid movement range, traverse to this cell and continue.
                            movementRangeGrid[newPosition.X, newPosition.Y] = true;
                            traverseAndUpdateMovementGridForCell(movementRangeGrid, newPosition, thisChar, remainingMovementValue - movementCost, new Point(directionsMovedSoFar.X | (int)direction.X, (int)directionsMovedSoFar.Y | direction.Y));
                        }
                    }
                }
            }


        }

        /// <summary>
        /// Draws each cell of the map with the tile image specified by the cell's TileID
        /// </summary>
        public void DrawMapCells(float scaleFactor)
        {
            Vector2 nextFreeCellPosition = new Vector2(0, 0);

            foreach (List<MapCell> row in TileMap)
            {
                foreach (MapCell cell in row)
                {
                    cell.Position = nextFreeCellPosition;
                    Rectangle tileToDraw = tileEngine.GetSourceRectangle(cell.TileID);

                    spriteBatch.Draw(tileSetTexture, nextFreeCellPosition, tileToDraw, Color.White, 0f, Vector2.Zero, scaleFactor, SpriteEffects.None, 0f);
                    spriteBatch.Draw(gridTexture, nextFreeCellPosition, null, Color.White, 0f, Vector2.Zero, scaleFactor, SpriteEffects.None, 0f);

                    nextFreeCellPosition.X += tileSize * scaleFactor;

                }

                nextFreeCellPosition.Y += tileSize * scaleFactor;
                nextFreeCellPosition.X = 0;
            }



        }

    }


    /// <summary>
    /// Represents an object that is displayed on a map
    /// </summary>
    [Serializable]
    public class MapObject
    {
        public Vector2 Position;
        //Size of object when displayed in pixels. If non displayable, it equals 0
        public int Size;

        public Boolean MouseIsOverMapObject(Vector2 cursorPosition, float scaleFactor)
        {
            Rectangle textureRect = new Rectangle((int)Position.X, (int)Position.Y, (int)(Size * scaleFactor), (int)(Size * scaleFactor));

            if (textureRect.Contains((int)cursorPosition.X, (int)cursorPosition.Y))
            {
                return true;
            }
            else
            {
                return false;
            }

        }

        public Boolean MapObjectHasBeenClicked(ButtonState currentButtonState, ButtonState lastButtonState, Vector2 cursorPosition, float scaleFactor)
        {

            if (MouseIsOverMapObject(cursorPosition, scaleFactor) == true)
            {
                if ((currentButtonState == ButtonState.Pressed) & (lastButtonState == ButtonState.Released))
                {
                    return true;
                }
            }

            return false;

        }

    }

    /// <summary>
    /// Represents a single square of a map displaying a tile image
    /// </summary>
    [Serializable]
    public class MapCell : MapObject
    {
        public int TileID;
        public int terrainType;
        public int movementCost = 1;


        public MapCell(int tileID, int mySize)
        {
            TileID = tileID;
            Size = mySize;
        }

    }

    [Serializable]
    public class Unit : MapObject
    {
        Character myCharacter;
        Texture2D unitTexture;
        SpriteBatch spriteBatch;
        private Boolean XmoveComplete = false;
        private Boolean YmoveComplete = false;
        public Boolean[,] movementRangeGrid;

        public Unit(int mySize, SpriteBatch mySpriteBatch, Vector2 myPosition, Texture2D myTexture)
        {
            spriteBatch = mySpriteBatch;
            Position = myPosition;
            unitTexture = myTexture;
            Size = mySize;
        }

        public void Draw(float scaleFactor)
        {
            //spriteBatch.Draw(unitTexture, Position, null, Color.White, 0f, Vector2.Zero, scaleFactor, SpriteEffects.None, 0f);
            spriteBatch.Draw(unitTexture, Position, null, Color.White, 0f, Vector2.Zero, scaleFactor, SpriteEffects.None, 0f);
        }

        /// <summary>
        /// Completes Unit movement operations.
        /// </summary>
        /// <param name="scaleFactor"></param>
        /// <param name="destinationPosition"></param>
        /// <param name="speed"></param>
        /// <returns> Returns true when move complete, false if it hasn't. Call repeatedly until the move has finished.</returns>
        public bool Move(float scaleFactor, Vector2 destinationPosition, int speed)
        {
            int XdirFactor = 0;
            int YdirFactor = 0;

            //Work out X direction of travel
            if (destinationPosition.X > Position.X)
            {
                XdirFactor = 1;
            }
            else
            {
                XdirFactor = -1;
            }

            //Work out Y direction of travel
            if (destinationPosition.Y > Position.Y)
            {
                YdirFactor = 1;
            }
            else
            {
                YdirFactor = -1;
            }


            if (XmoveComplete && YmoveComplete)
            {
                // Flag that the move is complete and exit.
                XmoveComplete = YmoveComplete = false;
                return true;
            }
            else if (XmoveComplete != true)
            {
                //Find out how far (and in what direction) we have to move
                float distanceToNewPosition = speed * scaleFactor * XdirFactor;

                Position.X += distanceToNewPosition;

                if (Position.X * XdirFactor >= destinationPosition.X * XdirFactor)
                {
                    XmoveComplete = true;
                    Position.X = destinationPosition.X;
                }

                return false;
            }
            else if (YmoveComplete != true)
            {

                float distanceToNewPosition = speed * scaleFactor * YdirFactor;

                Position.Y += distanceToNewPosition;

                if (Position.Y * YdirFactor >= destinationPosition.Y * YdirFactor)
                {
                    YmoveComplete = true;
                    Position.Y = destinationPosition.Y;
                }

                return false;
            }
            else
            {
                throw new Exception("Unit move failed. Something has gone very, very wrong.");
            }

        }

    }


    /// <summary>
    /// Performs tasks relating to the management of 2D tiles
    /// </summary>
    [Serializable]
    public class TileEngine
    {
        [NonSerializedAttribute, System.Xml.Serialization.XmlIgnoreAttribute]
        public Texture2D TileSetTexture;
        public int tilesize;
        public int tilesPerRow;

        public TileEngine(Texture2D myTileSetTexture, int myTileSize, int myTilesPerRow)
        {
            tilesize = myTileSize;
            TileSetTexture = myTileSetTexture;
            tilesPerRow = myTilesPerRow;
        }

        /// <summary>
        /// Extracts 2D tiles
        /// </summary>
        /// <param name="tileIndex"></param>
        /// <param name="tilesperrow"></param>
        /// <returns></returns>
        public Rectangle GetSourceRectangle(int tileIndex)
        {
            int yVal = 0;
            int xVal = 0;

            //if the tile index is greater than the number of tiles per row, that means that the function is trying 
            //to get a rectangle from the second(or third, or fourth etc) row.
            if (tileIndex > tilesPerRow)
            {

                //Find the row number
                yVal = (int)Math.Floor(tileIndex / (double)tilesPerRow);

                //Find the column number
                xVal = tileIndex - tilesPerRow;

            }

            return new Rectangle(tileIndex * tilesize, 0, tilesize, tilesize);
        }

    }

    static class Camera
    {
        static public Vector2 Location = Vector2.Zero;
    }
}
